package com.dci.intellij.dbn2.browser.ui;

import com.dci.intellij.dbn.browser.DatabaseBrowserManager;
import com.dci.intellij.dbn.browser.DatabaseBrowserUtils;
import com.dci.intellij.dbn.browser.TreeNavigationHistory;
import com.dci.intellij.dbn.browser.model.*;
import com.dci.intellij.dbn.browser.ui.DatabaseBrowserTreeCellRenderer;
import com.dci.intellij.dbn.browser.ui.ToolTipProvider;
import com.dci.intellij.dbn.common.dispose.Disposer;
import com.dci.intellij.dbn.common.dispose.Failsafe;
import com.dci.intellij.dbn.common.filter.Filter;
import com.dci.intellij.dbn.common.thread.Background;
import com.dci.intellij.dbn.common.thread.Dispatch;
import com.dci.intellij.dbn.common.thread.Progress;
import com.dci.intellij.dbn.common.thread.Timeout;
import com.dci.intellij.dbn.common.ui.GUIUtil;
import com.dci.intellij.dbn.common.ui.tree.DBNTree;
import com.dci.intellij.dbn.common.util.EventUtil;
import com.dci.intellij.dbn.connection.ConnectionBundle;
import com.dci.intellij.dbn.connection.ConnectionHandler;
import com.dci.intellij.dbn.connection.ConnectionManager;
import com.dci.intellij.dbn.connection.action.ConnectionActionGroup;
import com.dci.intellij.dbn.editor.EditorProviderId;
import com.dci.intellij.dbn.object.DBConsole;
import com.dci.intellij.dbn.object.action.ObjectActionGroup;
import com.dci.intellij.dbn.object.common.DBObject;
import com.dci.intellij.dbn.object.common.DBObjectBundle;
import com.dci.intellij.dbn.object.common.DBSchemaObject;
import com.dci.intellij.dbn.object.common.list.DBObjectList;
import com.dci.intellij.dbn.object.common.list.action.ObjectListActionGroup;
import com.dci.intellij.dbn.object.common.property.DBObjectProperty;
import com.dci.intellij.dbn.vfs.DatabaseFileSystem;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.project.Project;
import com.intellij.util.ui.tree.TreeUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.TreePath;
import java.awt.*;
import java.awt.event.*;

public class DatabaseBrowserTree extends DBNTree implements DataProvider {
    private BrowserTreeNode targetSelection;
    private JPopupMenu popupMenu;
    private TreeNavigationHistory navigationHistory = new TreeNavigationHistory();
    private DatabaseBrowserTreeSpeedSearch speedSearch;
    private boolean listenersEnabled = true;
    private Object lastClickTreeNode = null;
    private TreeSelectionListener treeSelectionListener = new TreeSelectionListener() {
        public void valueChanged(TreeSelectionEvent e) {
            if (Failsafe.check(this) && DatabaseBrowserTree.this.listenersEnabled) {
                Object object = e.getPath().getLastPathComponent();
                if (object instanceof BrowserTreeNode) {
                    BrowserTreeNode treeNode = (BrowserTreeNode)object;
                    if (DatabaseBrowserTree.this.targetSelection == null || treeNode.equals(DatabaseBrowserTree.this.targetSelection)) {
                        DatabaseBrowserTree.this.navigationHistory.add(treeNode);
                    }
                }

                EventUtil.notify(DatabaseBrowserTree.this.getProject(), BrowserTreeEventListener.TOPIC, (listener) -> {
                    listener.selectionChanged();
                });
            }

        }
    };


    private MouseListener mouseListener = new MouseAdapter() {
        public void mouseClicked(MouseEvent event) {
            if (event.getButton() == 1) {
                DatabaseBrowserManager browserManager = DatabaseBrowserManager.getInstance(DatabaseBrowserTree.this.getProject());
                if ((Boolean)browserManager.getAutoscrollToEditor().value() || event.getClickCount() > 1) {
                    TreePath path = DatabaseBrowserTree.this.getPathForLocation(event.getX(), event.getY());
                    DatabaseBrowserTree.this.processSelectEvent(event, path, event.getClickCount() > 1);
                }
            }

        }

        public void mouseReleased(MouseEvent event) {
            if (event.getButton() == 3) {
                TreePath path = DatabaseBrowserTree.this.getPathForLocation(event.getX(), event.getY());
                if (path != null) {
                    BrowserTreeNode lastPathEntity = (BrowserTreeNode)path.getLastPathComponent();
                    if (lastPathEntity.isDisposed()) {
                        return;
                    }
                    DatabaseBrowserTree.this.lastClickTreeNode = lastPathEntity;
                    Progress.modal(lastPathEntity.getProject(), "Loading object information", true, (progress) -> {
                        ActionGroup actionGroup = null;
                        if (lastPathEntity instanceof DBObjectList) {
                            DBObjectList objectList = (DBObjectList)lastPathEntity;
                            actionGroup = new ObjectListActionGroup(objectList);
                            ActionGroup extensionObjectGroup = (ActionGroup) ActionManager.getInstance().getAction("DBNavigator2.ActionGroup.Browser.DbObject.PopupMenu");
                            ((DefaultActionGroup)actionGroup).addAll(extensionObjectGroup);
                        } else if (lastPathEntity instanceof DBObject) {
                            actionGroup = new ObjectActionGroup((DBObject)lastPathEntity);
                            ActionGroup extensionObjectGroup = (ActionGroup) ActionManager.getInstance().getAction("DBNavigator2.ActionGroup.Browser.DbObject.PopupMenu");
                            ((DefaultActionGroup)actionGroup).addAll(extensionObjectGroup);
                        } else if (lastPathEntity instanceof DBObjectBundle) {
                            DBObjectBundle objectsBundle = (DBObjectBundle)lastPathEntity;
                            ConnectionHandler connectionHandler = objectsBundle.getConnectionHandler();
                            actionGroup = new ConnectionActionGroup(connectionHandler);
                            ActionGroup extensionObjectGroup = (ActionGroup) ActionManager.getInstance().getAction("DBNavigator2.ActionGroup.Browser.DbObject.PopupMenu");
                            ((DefaultActionGroup)actionGroup).addAll(extensionObjectGroup);
                        }

                        if (actionGroup != null) {
                            Progress.check(progress);
                            ActionPopupMenu actionPopupMenu = ActionManager.getInstance().createActionPopupMenu("", (ActionGroup)actionGroup);
                            DatabaseBrowserTree.this.popupMenu = actionPopupMenu.getComponent();
                            Dispatch.run(() -> {
                                if (DatabaseBrowserTree.this.isShowing()) {
                                    DatabaseBrowserTree.this.popupMenu.show(DatabaseBrowserTree.this, event.getX(), event.getY());
                                }

                            });
                        } else {
                            DatabaseBrowserTree.this.popupMenu = null;
                        }

                    });
                }
            }

        }
    };
    private KeyListener keyListener = new KeyAdapter() {
        public void keyPressed(KeyEvent e) {
            if (e.getKeyCode() == 10) {
                TreePath path = DatabaseBrowserTree.this.getSelectionPath();
                DatabaseBrowserTree.this.processSelectEvent(e, path, true);
            }

        }
    };

    public DatabaseBrowserTree(BrowserTreeModel treeModel) {
        super(treeModel.getProject(), treeModel);
        this.addKeyListener(this.keyListener);
        this.addMouseListener(this.mouseListener);
        this.addTreeSelectionListener(this.treeSelectionListener);
        this.setToggleClickCount(0);
        this.setRootVisible(treeModel instanceof TabbedBrowserTreeModel);
        this.setShowsRootHandles(true);
        this.setAutoscrolls(true);
        DatabaseBrowserTreeCellRenderer browserTreeCellRenderer = new DatabaseBrowserTreeCellRenderer(treeModel.getProject());
        this.setCellRenderer(browserTreeCellRenderer);
        this.speedSearch = new DatabaseBrowserTreeSpeedSearch(this);
    }

    public BrowserTreeModel getModel() {
        return (BrowserTreeModel)super.getModel();
    }

    public TreeNavigationHistory getNavigationHistory() {
        return this.navigationHistory;
    }

    public void expandConnectionManagers() {
        Dispatch.run(() -> {
            ConnectionManager connectionManager = ConnectionManager.getInstance(this.getProject());
            ConnectionBundle connectionBundle = connectionManager.getConnectionBundle();
            TreePath treePath = DatabaseBrowserUtils.createTreePath(connectionBundle);
            this.setExpandedState(treePath, true);
        });
    }

    public void selectElement(BrowserTreeNode treeNode, boolean focus) {
        if (treeNode != null) {
            ConnectionHandler connectionHandler = treeNode.getConnectionHandler();
            Filter<BrowserTreeNode> filter = connectionHandler.getObjectTypeFilter();
            if (filter.accepts(treeNode)) {
                this.targetSelection = treeNode;
                this.scrollToSelectedElement();
                if (focus) {
                    this.requestFocus();
                }
            }
        }

    }

    public void scrollToSelectedElement() {
        if (this.getProject().isOpen() && this.targetSelection != null) {
            Background.run(() -> {
                BrowserTreeNode targetSelection = this.targetSelection;
                if (targetSelection != null) {
                    targetSelection = (BrowserTreeNode)targetSelection.getUndisposedElement();
                    TreePath treePath = DatabaseBrowserUtils.createTreePath(targetSelection);
                    if (treePath != null) {
                        Object[] var3 = treePath.getPath();
                        int var4 = var3.length;

                        for(int var5 = 0; var5 < var4; ++var5) {
                            Object object = var3[var5];
                            BrowserTreeNode treeNode = (BrowserTreeNode)object;
                            if (!Failsafe.check(treeNode)) {
                                this.targetSelection = null;
                                return;
                            }

                            if (treeNode.equals(targetSelection)) {
                                break;
                            }

                            if (!treeNode.isLeaf() && !treeNode.isTreeStructureLoaded()) {
                                this.selectPath(DatabaseBrowserUtils.createTreePath(treeNode));
                                treeNode.getChildren();
                                return;
                            }
                        }

                        this.targetSelection = null;
                        this.selectPath(treePath);
                    }
                }

            });
        }

    }

    public BrowserTreeNode getSelectedNode() {
        TreePath selectionPath = this.getSelectionPath();
        return selectionPath == null ? null : (BrowserTreeNode)selectionPath.getLastPathComponent();
    }

    public BrowserTreeNode getTargetSelection() {
        return this.targetSelection;
    }

    private void selectPath(TreePath treePath) {
        Dispatch.run(() -> {
            TreeUtil.selectPath(this, treePath, true);
        });
    }

    public String getToolTipText(MouseEvent event) {
        TreePath path = this.getClosestPathForLocation(event.getX(), event.getY());
        if (path != null) {
            Rectangle pathBounds = this.getPathBounds(path);
            if (pathBounds != null) {
                Point mouseLocation = GUIUtil.getRelativeMouseLocation(event.getComponent());
                if (pathBounds.contains(mouseLocation)) {
                    Object object = path.getLastPathComponent();
                    if (object instanceof ToolTipProvider) {
                        ToolTipProvider toolTipProvider = (ToolTipProvider)object;
                        return (String) Timeout.call(1L, (Object)null, true, () -> {
                            return toolTipProvider.getToolTip();
                        });
                    }
                }
            }
        }

        return null;
    }

    public void navigateBack() {
        BrowserTreeNode treeNode = this.navigationHistory.previous();
        if (treeNode != null) {
            this.selectPathSilently(DatabaseBrowserUtils.createTreePath(treeNode));
        }

    }

    public void navigateForward() {
        BrowserTreeNode treeNode = this.navigationHistory.next();
        if (treeNode != null) {
            this.selectPathSilently(DatabaseBrowserUtils.createTreePath(treeNode));
        }

    }

    private void selectPathSilently(TreePath treePath) {
        if (treePath != null) {
            this.listenersEnabled = false;
            this.selectionModel.setSelectionPath(treePath);
            TreeUtil.selectPath(this, treePath, true);
            this.listenersEnabled = true;
        }

    }

    public void expandAll() {
        BrowserTreeNode root = this.getModel().getRoot();
        this.expand(root);
    }

    public void expand(BrowserTreeNode treeNode) {
        if (treeNode.canExpand()) {
            this.expandPath(DatabaseBrowserUtils.createTreePath(treeNode));

            for(int i = 0; i < treeNode.getChildCount(); ++i) {
                BrowserTreeNode childTreeNode = treeNode.getChildAt(i);
                this.expand(childTreeNode);
            }
        }

    }

    public void collapseAll() {
        BrowserTreeNode root = this.getModel().getRoot();
        this.collapse(root);
    }

    private void collapse(BrowserTreeNode treeNode) {
        if (!treeNode.isLeaf() && treeNode.isTreeStructureLoaded()) {
            for(int i = 0; i < treeNode.getChildCount(); ++i) {
                BrowserTreeNode childTreeNode = treeNode.getChildAt(i);
                this.collapse(childTreeNode);
                this.collapsePath(DatabaseBrowserUtils.createTreePath(childTreeNode));
            }
        }

    }

    private void processSelectEvent(InputEvent event, TreePath path, boolean deliberate) {
        if (path != null) {
            Object lastPathEntity = path.getLastPathComponent();
            DBConsole console;
            if (lastPathEntity instanceof DBObject) {
                DBObject object = (DBObject)lastPathEntity;
                DatabaseBrowserTree.this.lastClickTreeNode = lastPathEntity;
                DatabaseFileSystem databaseFileSystem = DatabaseFileSystem.getInstance();
                Project project = this.getProject();
                if (object instanceof DBConsole) {
                    console = (DBConsole)object;
                    FileEditorManager fileEditorManager = FileEditorManager.getInstance(project);
                    fileEditorManager.openFile(console.getVirtualFile(), true);
                    event.consume();
                } else if (object.is(DBObjectProperty.EDITABLE)) {
                    DBSchemaObject schemaObject = (DBSchemaObject)object;
                    databaseFileSystem.connectAndOpenEditor(schemaObject, (EditorProviderId)null, false, deliberate);
                    event.consume();
                } else if (object.is(DBObjectProperty.NAVIGABLE)) {
                    databaseFileSystem.connectAndOpenEditor(object, (EditorProviderId)null, false, deliberate);
                    event.consume();
                } else if (deliberate) {
                    Progress.prompt(project, "Loading object reference", true, (progress) -> {
                        DBObject navigationObject = object.getDefaultNavigationObject();
                        if (navigationObject != null) {
                            Progress.check(progress);
                            Dispatch.run(() -> {
                                navigationObject.navigate(true);
                            });
                        }

                    });
                }
            } else if (lastPathEntity instanceof DBObjectBundle) {
                DBObjectBundle objectBundle = (DBObjectBundle)lastPathEntity;
                ConnectionHandler connectionHandler = objectBundle.getConnectionHandler();
                FileEditorManager fileEditorManager = FileEditorManager.getInstance(connectionHandler.getProject());
                console = connectionHandler.getConsoleBundle().getDefaultConsole();
                fileEditorManager.openFile(console.getVirtualFile(), deliberate);
            }
        }

    }

    public void disposeInner() {
        Disposer.dispose(new Object[]{this.speedSearch, this.navigationHistory});
        super.disposeInner();
        this.setModel(new SimpleBrowserTreeModel());
    }

    @Nullable
    @Override
    public Object getData(@NotNull String s) {
        if (DatabaseViewUtils.LAST_CLICK_TREE_NODE.is(s)) {
            return lastClickTreeNode;
        }
        return null;
    }
}
